package com.px.task.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.*;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson2.JSONObject;
import com.px.common.utils.StringUtils;
import com.px.device.domain.RoutesInfo;
import com.px.device.service.IRoutesInfoService;
import com.px.quartz.domain.SysJob;
import com.px.quartz.service.ISysJobService;
import com.px.task.domain.TaskCalendar;
import com.px.task.domain.TaskCalendarBlacklist;
import com.px.task.enums.TaskCalendarType;
import com.px.task.mapper.TaskCalendarMapper;
import com.px.task.service.ITaskCalendarBlacklistService;
import com.px.task.service.ITaskCalendarService;
import com.px.task.service.ITaskRecordService;
import com.px.task.vo.TaskVo;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronExpression;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 任务管理Service业务层处理
 * 
 * @author WRJ
 * @date 2024-08
 */
@Slf4j
@Service
public class TaskCalendarServiceImpl implements ITaskCalendarService 
{
    private final String JOB_REGISTER_TYPE_ADD = "ADD";
    private final String JOB_REGISTER_TYPE_UPDATE = "UPDATE";
    private final String JOB_REGISTER_TYPE_DELETE = "DELETE";
    private final String CRON_SEPARATOR = "&&";

    @Autowired
    private TaskCalendarMapper taskCalendarMapper;

    @Autowired
    private ISysJobService jobService;
    @Autowired
    private ITaskCalendarBlacklistService taskCalendarBlacklistService;
    @Autowired
    private ITaskRecordService taskRecordService;
    @Autowired
    private IRoutesInfoService routesInfoService;

    /**
     * 查询任务管理
     * 
     * @param id 任务管理主键
     * @return 任务管理
     */
    @Override
    public TaskCalendar selectTaskCalendarById(Long id)
    {
        return taskCalendarMapper.selectTaskCalendarById(id);
    }

    /**
     * 查询任务管理列表
     * 
     * @param taskCalendar 任务管理
     * @return 任务管理
     */
    @Override
    public List<TaskCalendar> selectTaskCalendarList(TaskCalendar taskCalendar)
    {
        return taskCalendarMapper.selectTaskCalendarList(taskCalendar);
    }

    /**
     * 新增任务管理
     * 
     * @param taskCalendar 任务管理
     * @return 结果
     */
    @Override
    public int insertTaskCalendar(TaskCalendar taskCalendar)
    {
        String cron;
        if (3 == taskCalendar.getTaskType()) {
            cron = Arrays.stream(taskCalendar.getTaskTime().split(","))
                    .map(taskTime -> generateCron(taskCalendar, taskTime)).collect(Collectors.joining(CRON_SEPARATOR));
        }else {
            cron = generateCron(taskCalendar, null);
        }
        taskCalendar.setTaskCron(cron);
        int result = taskCalendarMapper.insertTaskCalendar(taskCalendar);

        String sysJobId = registerToSysJob(taskCalendar, JOB_REGISTER_TYPE_ADD);
        taskCalendar.setSysJobId(sysJobId);
        taskCalendarMapper.updateTaskCalendar(taskCalendar);
        return result;
    }

    /**
     * 修改任务管理
     * 
     * @param taskCalendar 任务管理
     * @return 结果
     */
    @Override
    public int updateTaskCalendar(TaskCalendar taskCalendar)
    {
        String cron;
        if (3 == taskCalendar.getTaskType()) {
            cron = Arrays.stream(taskCalendar.getTaskTime().split(","))
                    .map(taskTime -> generateCron(taskCalendar, taskTime)).collect(Collectors.joining(CRON_SEPARATOR));
        }else {
            cron = generateCron(taskCalendar, null);
        }
        taskCalendar.setTaskCron(cron);
        int result = taskCalendarMapper.updateTaskCalendar(taskCalendar);

        String sysJobId = registerToSysJob(taskCalendar, JOB_REGISTER_TYPE_UPDATE);
        taskCalendar.setSysJobId(sysJobId);
        taskCalendarMapper.updateTaskCalendar(taskCalendar);
        return result;
    }

    /**
     * 批量删除任务管理
     * 
     * @param ids 需要删除的任务管理主键
     * @return 结果
     */
    @Override
    public int deleteTaskCalendarByIds(Long[] ids)
    {
        Arrays.stream(ids).forEach(id->{
            TaskCalendar taskCalendar = selectTaskCalendarById(id);
            registerToSysJob(taskCalendar, JOB_REGISTER_TYPE_DELETE);
        });
        return taskCalendarMapper.deleteTaskCalendarByIds(ids);
    }

    /**
     * 删除任务管理信息
     * 
     * @param id 任务管理主键
     * @return 结果
     */
    @Override
    public int deleteTaskCalendarById(Long id)
    {
        Long[] ids = {id};
        return deleteTaskCalendarByIds(ids);
    }

    private String generateCron(TaskCalendar taskCalendar, String taskTime) {
        String secondCron = "*";
        String minuteCron = "*";
        String hourCron = "*";
        String dayCron = "*";
        String monthCron = "*";
        String weekCron = "?";

        switch (taskCalendar.getTaskType()) {
            case 1:
                // 立即执行
                secondCron = DateUtil.second(new Date()) + "";
                minuteCron = DateUtil.minute(new Date()) + "";
                hourCron = DateUtil.hour(new Date(), true) + "";
                dayCron = DateUtil.dayOfMonth(new Date()) + "";
                monthCron = DateUtil.month(new Date()) + 1 + "";
                weekCron = "?";
                break;
            case 2:
                // 定制执行
                Date estimatedTime = taskCalendar.getTaskEstimatedTime();
                secondCron = DateUtil.second(estimatedTime) + "";
                minuteCron = DateUtil.minute(estimatedTime) + "";
                hourCron = DateUtil.hour(estimatedTime, true) + "";
                dayCron = DateUtil.dayOfMonth(estimatedTime) + "";
                monthCron = DateUtil.month(estimatedTime) + 1 + "";
                weekCron = "?";
                break;
            case 3:
                // 循环模式
                Assert.hasLength(taskTime, "循环模式下需指定执行时间");
                // 取后两位
                secondCron = taskTime.substring(4);
                // 取中间两位
                minuteCron = taskTime.substring(2, 4);
                // 取前两位
                hourCron = taskTime.substring(0, 2);

                // 严格按照创建时间来计算每几天/每周几执行任务,系统定时任务每天按时触发,自己来计算每几天/每周几
//                if ("DAY".equals(taskCalendar.getTaskCycleMode())) {
//                    Assert.isTrue(NumberUtil.isNumber(taskCalendar.getTaskInterval()), "按天循环时,间隔时间非数字类型.");
//                    dayCron = String.format("*/%s", NumberUtil.parseLong(taskCalendar.getTaskInterval()));
//                } else {
//                    dayCron = "?";
//                }
//                monthCron = "*";
//                if ("WEEK".equals(taskCalendar.getTaskCycleMode())) {
//                    weekCron = taskCalendar.getTaskInterval();
//                } else {
//                    weekCron = "?";
//                }
                break;
            default:
                break;
        }
        String cron = String.format("%s %s %s %s %s %s", secondCron, minuteCron, hourCron, dayCron, monthCron, weekCron);
        if (!CronExpression.isValidExpression(cron)) {
            throw new RuntimeException("生成无效Cron表达式!cronExpression=" + cron);
        }
        return cron;
    }

    @Override
    public List<TaskVo> getMonthTaskByMonthAndType(Date date) {
        List<TaskVo> result = new ArrayList<>();
        List<TaskCalendar> taskCalendars = taskCalendarMapper.selectTaskCalendarInMonth(DateUtil.beginOfMonth(date).toString(), DateUtil.endOfMonth(date).toString());
        if (CollectionUtil.isEmpty(taskCalendars)) {
            return result;
        }
        Long[] wayPointIds = taskCalendars.stream().map(TaskCalendar::getWayPointId).toArray(Long[]::new);
        Map<Long, RoutesInfo> routesMap = new HashMap<>();
        routesInfoService.selectRoutesInfoByRoutesIds(wayPointIds).forEach(r-> routesMap.put(r.getRoutesId(), r));
        taskCalendars.forEach(taskCalendar -> {
            List<Date> execDates = new ArrayList<>();
            Date begin =  DateUtil.beginOfMonth(date);
            Date end =  DateUtil.endOfMonth(date);
            if (TaskCalendarType.NOW.getType() == taskCalendar.getTaskType()) {
                // 时间范围从月初到月末
                for (String cron : taskCalendar.getTaskCron().split(CRON_SEPARATOR)) {
                    execDates.addAll(calculateDates(cron, begin, end));
                }
            }
            if (TaskCalendarType.TIMING.getType() == taskCalendar.getTaskType()) {
                // 时间范围从月初到月末
                for (String cron : taskCalendar.getTaskCron().split(CRON_SEPARATOR)) {
                    execDates.addAll(calculateDates(cron, begin, end));
                }
            }
            if (TaskCalendarType.CYCLE.getType() == taskCalendar.getTaskType()) {
                // 循环模式下开始时间用 月初时间和生效时间比 取较晚时间
                begin = DateUtil.beginOfMonth(date).after(taskCalendar.getTaskValidTime()) ? DateUtil.beginOfMonth(date) : taskCalendar.getTaskValidTime();
                // 循环模式下结束时间用 月末时间和失效时间比 取较早时间
                end = DateUtil.endOfMonth(date).before(taskCalendar.getTaskInvalidTime()) ? DateUtil.endOfMonth(date) : taskCalendar.getTaskInvalidTime();


                if (StringUtils.equals("DAY", taskCalendar.getTaskCycleMode())) {
                    for (String taskTime : taskCalendar.getTaskTime().split(",")) {
                        String startStr = DateUtil.format(taskCalendar.getTaskValidTime(), DatePattern.PURE_DATE_PATTERN) + taskTime;
                        DateTime startDate = DateUtil.parse(startStr, DatePattern.PURE_DATETIME_PATTERN);
                        execDates.addAll(calculateDatesByDay(NumberUtil.parseInt(taskCalendar.getTaskInterval()), startDate, begin, end));
                    }
                }
                if (StringUtils.equals("WEEK", taskCalendar.getTaskCycleMode())) {
                    for (String taskTime : taskCalendar.getTaskTime().split(",")) {
                        String startStr = DateUtil.format(taskCalendar.getTaskValidTime(), DatePattern.PURE_DATE_PATTERN) + taskTime;
                        DateTime startDate = DateUtil.parse(startStr, DatePattern.PURE_DATETIME_PATTERN);
                        execDates.addAll(calculateDatesByWeek(taskCalendar.getTaskInterval(), startDate, begin, end));
                    }
                }
            }

            for (Date execDate : execDates) {
                TaskVo taskVo = new TaskVo();
                taskVo.setTask(taskCalendar.getId());
                taskVo.setName(taskCalendar.getTaskName());
                taskVo.setTaskType(taskCalendar.getTaskType());
                taskVo.setTaskDesc(taskCalendar.getTaskDesc());
                taskVo.setRoutesName(routesMap.get(taskCalendar.getWayPointId()).getRoutesName());
                taskVo.setTime(execDate);
                result.add(taskVo);
            }
        });

        // 针对循环任务中,去掉被删除的单次任务
        if (CollectionUtil.isNotEmpty(result)) {
            List<Long> execTaskIds = result.stream().map(TaskVo::getTask).collect(Collectors.toList());
            List<TaskCalendarBlacklist> blacklists = taskCalendarBlacklistService.selectTaskCalendarBlacklistListByTaskId(execTaskIds);
            result.removeIf(execDate ->
                    blacklists.stream().anyMatch(blacklist ->
                            NumberUtil.equals(blacklist.getTaskId(), execDate.getTask())
                                    && 5 > DateUtil.between(blacklist.getTaskExecTime(), execDate.getTime(), DateUnit.MINUTE)
                    )
            );
        }
        if (CollectionUtil.isEmpty(result)) {
            return new ArrayList<>();
        }
        List<TaskVo> execVo = result.stream().sorted(Comparator.comparing(TaskVo::getTime)).collect(Collectors.toList());
        return execVo;
    }


    /**
     * 重新序列化返回格式
     * @param result
     * @return
     */
    public List<JSONObject> processResultFormat(List<TaskVo> result) {

        List<JSONObject> rr = new ArrayList<>();

        if (CollectionUtil.isEmpty(result)) {
            return new ArrayList<>();
        }
        Map<String, List<TaskVo>> collect = result.stream().collect(Collectors.groupingBy(task -> DateUtil.formatDate(task.getTime())));

        collect.forEach((key, value) -> {
            JSONObject dayTask = new JSONObject();
            dayTask.put("date", key);
            dayTask.put("task", value);
            rr.add(dayTask);
        });
        rr.sort(Comparator.comparing(j -> j.getString("date")));
        return rr;
    }

    @Override
    public String execTask(Long taskId) {
        // 判断此任务是否被删除(如果触发执行时间与被删除执行时间小于5分钟,视为同意任务,不予执行)
        List<TaskCalendarBlacklist> blacklists = taskCalendarBlacklistService.selectTaskCalendarBlacklistListByTaskId(Collections.singletonList(taskId));
        boolean isBlacklistTask = blacklists.stream()
                .anyMatch(blacklist -> 5 > DateUtil.between(blacklist.getTaskExecTime(), new Date(), DateUnit.MINUTE, true));
        if (isBlacklistTask) {
            return "此任务已被删除,不再执行";
        }
        TaskCalendar taskCalendar = taskCalendarMapper.selectTaskCalendarById(taskId);
        if (null == taskCalendar) {
            return String.format("任务ID不存在,执行失败!任务ID:%s", taskId);
        }
        if (TaskCalendarType.CYCLE.getType() == taskCalendar.getTaskType()) {
            // 如果是循环任务,判断是否在有效期内
            if (!DateUtil.isIn(new Date(), taskCalendar.getTaskValidTime(), taskCalendar.getTaskInvalidTime())) {
                String msg = String.format("超出任务指定的生效时间段,不予执行!任务名称:%s,生效时间段:%s ~ %s",
                        taskCalendar.getTaskName(),
                        DateUtil.formatDateTime(taskCalendar.getTaskValidTime()),
                        DateUtil.formatDateTime(taskCalendar.getTaskInvalidTime()));
                return msg;
            }
            // 如果是循环任务,判断是不是再间隔时间中
            if (StringUtils.equals("WEEK", taskCalendar.getTaskCycleMode())) {
                if (!isMatchTask(new Date(), taskCalendar, "WEEK")) {
                    return "跳过";
                }
            }
            if (StringUtils.equals("DAY", taskCalendar.getTaskCycleMode())) {
                if (!isMatchTask(new Date(), taskCalendar, "DAY")) {
                    return "跳过";
                }
            }
        }
        try {
            taskRecordService.insertTaskRecordByCalendar(taskId);
        } catch (Exception e) {
            log.error("任务执行失败" , e);
            return e.getMessage();
        }
        return "任务执行完成!";
    }

    private boolean isMatchTask(Date execDate, TaskCalendar taskCalendar, String mode) {
        if ("DAY".equals(mode)) {
            String beginStr = DateUtil.format(taskCalendar.getTaskValidTime(), DatePattern.PURE_DATE_PATTERN) + taskCalendar.getTaskTime();
            DateTime beginDate = DateUtil.parse(beginStr, DatePattern.PURE_DATETIME_PATTERN);

            List<Date> dates = calculateDatesByDay(NumberUtil.parseInt(taskCalendar.getTaskInterval()), beginDate,
                    DateUtil.offsetDay(execDate, -1), DateUtil.offsetDay(execDate, 1));
            if (CollectionUtil.isEmpty(dates)) {
                return false;
            }
            return dates.stream().anyMatch(d -> 5 > DateUtil.between(execDate, d, DateUnit.MINUTE, true));
        }
        if ("WEEK".equals(mode)) {
            String beginStr = DateUtil.format(taskCalendar.getTaskValidTime(), DatePattern.PURE_DATE_PATTERN) + taskCalendar.getTaskTime();
            DateTime beginDate = DateUtil.parse(beginStr, DatePattern.PURE_DATETIME_PATTERN);

            List<Date> dates = calculateDatesByWeek(taskCalendar.getTaskInterval(), beginDate,
                    DateUtil.offsetDay(execDate, -1), DateUtil.offsetDay(execDate, 1));
            if (CollectionUtil.isEmpty(dates)) {
                return false;
            }
            return dates.stream().anyMatch(d -> 5 > DateUtil.between(execDate, d, DateUnit.MINUTE, true));
        }
        return false;
    }

    /**
     * 按照CRON计算时间范围内的合适日期
     *
     * @param cronExpression
     * @param beginDate
     * @param endDate
     * @return
     */
    private List<Date> calculateDates(String cronExpression, Date beginDate, Date endDate) {
        List<Date> dates = new ArrayList<>();
        CronExpression cron = null;
        try {
            cron = new CronExpression(cronExpression);
        } catch (Exception e) {
            return dates;
        }
        Date nextDate = cron.getNextValidTimeAfter(beginDate);
        while (null != nextDate && nextDate.before(endDate)) {
            dates.add(nextDate);
            nextDate = cron.getNextValidTimeAfter(nextDate);
        }
        return dates;
    }

    /**
     * 计算时间范围内,从开始时间算起,每过n天的日期时间集合
     *
     * @param dayInterval 时间间隔(天)
     * @param beginDate   开始计算时间
     * @param validDate   时间范围起
     * @param invalidDate 时间范围止
     * @return
     */
    public List<Date> calculateDatesByDay(Integer dayInterval, Date beginDate, Date validDate, Date invalidDate) {
        List<Date> dates = new ArrayList<>();
        Date execDate = beginDate;
        while (true) {
            if (execDate.after(invalidDate)) {
                break;
            }
            if (execDate.after(validDate)) {
                dates.add(execDate);
            }
            execDate = DateUtil.offsetDay(execDate, dayInterval);
        }
        return dates;
    }

    /**
     * 计算时间范围内,从开始时间算起,每周几的日期时间集合
     * @param weekInterval   周几
     * @param beginDate      开始计算时间
     * @param validDate 时间范围起
     * @param invalidDate 时间范围止
     * @return
     */
    public List<Date> calculateDatesByWeek(String weekInterval, Date beginDate, Date validDate, Date invalidDate) {
        List<Date> dates = new ArrayList<>();

        List<Integer> weekNumber = Arrays.stream(weekInterval.split(",")).map(NumberUtil::parseInt).collect(Collectors.toList());
        validDate = DateUtil.beginOfDay(validDate);
        validDate = DateUtil.offsetHour(validDate, DateUtil.hour(beginDate, true))
                .offset(DateField.MINUTE, DateUtil.minute(beginDate))
                .offset(DateField.SECOND, DateUtil.second(beginDate));

        Date execDate = validDate;
        while (true) {
            if (execDate.after(invalidDate)) {
                break;
            }
            if (weekNumber.contains(DateUtil.dayOfWeek(execDate))) {
                dates.add(execDate);
            }
            execDate = DateUtil.offsetDay(execDate, 1);
        }
        return dates;

    }

    /**
     * 注册到系统定时任务
     * @param taskCalendar
     * @param type ADD=新增,UPDATE=修改,DELETE=删除
     */
    private String registerToSysJob(TaskCalendar taskCalendar, String type) {
        // 如果是 立即执行 任务则直接执行
        if (TaskCalendarType.NOW.getType() == taskCalendar.getTaskType()) {
            execTask(taskCalendar.getId());
            return null;
        }
        // 修改或者删除先全部删除然后再新增
        if (StringUtils.equals(JOB_REGISTER_TYPE_UPDATE, type) ||
                StringUtils.equals(JOB_REGISTER_TYPE_DELETE, type)) {
            String[] split = selectTaskCalendarById(taskCalendar.getId()).getSysJobId().split(",");
            Long[] ids = new Long[split.length];
            for (int i = 0; i < split.length; i++) {
                ids[i] = NumberUtil.parseLong(split[i]);
            }
            try {
                jobService.deleteJobByIds(ids);
            } catch (SchedulerException e) {
                log.error("巡航任务操作失败!", e.getMessage());
                throw new RuntimeException("巡航任务操作失败!" + e.getMessage());
            }
        }
        if (StringUtils.equals(JOB_REGISTER_TYPE_DELETE, type)) {
            // 如果是删除任务,不再添加定时任务
            return null;
        }
        List<Long> jobIds = new ArrayList<>();
        if (StringUtils.isNotEmpty(taskCalendar.getTaskCron())) {
            String[] crons = taskCalendar.getTaskCron().split(CRON_SEPARATOR);
            for (int i = 0; i < crons.length; i++) {
                // 新增任务
                jobIds.add(registerToSysJob(crons[i], taskCalendar.getId(), taskCalendar.getTaskName(), JOB_REGISTER_TYPE_ADD, taskCalendar.getCreateBy()));
            }
        }
        return jobIds.stream().map(e -> e + "").collect(Collectors.joining(","));
    }

    private Long registerToSysJob(String cron, Long taskId, String jobName, String type, String createBy) {
        SysJob sysJob = new SysJob();
        sysJob.setJobName(jobName);
        sysJob.setJobGroup("TASK");
        sysJob.setInvokeTarget(String.format("taskCalendarServiceImpl.execTask(%sL)", taskId));
        sysJob.setCronExpression(cron);
        sysJob.setMisfirePolicy("1");
        sysJob.setConcurrent("1");
        sysJob.setStatus("0");
        sysJob.setCreateBy(createBy);
        sysJob.setCreateTime(new Date());
        try {
            jobService.insertJob(sysJob);
            jobService.resumeJob(sysJob);
        } catch (Exception e) {
            log.error("巡航任务保存失败!", e.getMessage());
            throw new RuntimeException("任务保存失败!" + e.getMessage());
        }
        return sysJob.getJobId();
    }
}
